#include "stdafx.h"
#include "ZOdbcRecordset.h"
#include "ZOdbcConnection.h"
#include "ZStream.h"
#include <assert.h>

namespace	Z	{

namespace	Odbc	{

//------------------------------------------------------------------------------------------------------------------------
Recordset::Column::Column()
:	mColBuf(nullptr),
	mColName(nullptr),
	mNullData(false)
{
}

Recordset::Column::Column(Connection& kConnection)
:	mColBuf((char*)kConnection.allocateUnaligned(colBufSize)),
	mColName((SQLCHAR*)kConnection.allocateUnaligned(colNameSize * sizeof(SQLCHAR))),
	mNullData(false)
{
}

Recordset::Column::~Column()
{
}

//------------------------------------------------------------------------------------------------------------------------
Recordset::Recordset(Connection& kConnection)
:	mStmt(0),
	mNumResultCols(0),
	mBound(false),
	mConnection(kConnection)
{
	::SQLAllocHandle(SQL_HANDLE_STMT, kConnection.dbc(), &mStmt);

	assert(mStmt != 0);
}

Recordset::~Recordset()
{
	unbind();

	::SQLFreeHandle(SQL_HANDLE_STMT, mStmt);
}

void	Recordset::bind()
{
	mColumns.clear();

	::SQLNumResultCols(mStmt, &mNumResultCols);
	if(mNumResultCols == 0)
		return;
	for(SQLSMALLINT columnNumber = 1; columnNumber <= mNumResultCols; ++columnNumber)	{
		Column column(mConnection);
		SQLINTEGER strLen_or_Ind	= 0;
		::SQLBindCol(mStmt, columnNumber, SQL_C_CHAR, column.colBuf(), Column::colBufSize, &strLen_or_Ind);
		column.setNullData(strLen_or_Ind == SQL_NULL_DATA);
		SQLSMALLINT kNameLength	= 0;
		SQLSMALLINT kDataType;
		SQLUINTEGER kColumnSize;
		SQLSMALLINT kDecimalDigits;
		SQLSMALLINT kNullable;
		::SQLDescribeCol(mStmt, columnNumber, column.colName(), Column::colNameSize, &kNameLength, &kDataType, &kColumnSize, &kDecimalDigits, &kNullable);
		mColumns.push_back(column);
	}
	mBound	= true;
}

void Recordset::unbind()
{
	if(!mBound)
		return;

	mColumns.clear();
	::SQLCloseCursor(mStmt);
	::SQLFreeStmt(mStmt, SQL_UNBIND);
	mBound	= false;
}

bool Recordset::fetch()
{
	const SQLRETURN retcode	= ::SQLFetch(mStmt);
	return SQL_SUCCEEDED(retcode) || retcode == SQL_NEED_DATA;
}

bool	Recordset::moreResults()
{
	const SQLRETURN retcode	= ::SQLMoreResults(mStmt);
	if(SQL_SUCCEEDED(retcode))	{
		diagnosis(SQL_HANDLE_STMT, mStmt);
		return false;
	}
	bind();
	return true;
}

const Recordset::Column* Recordset::findColumn(const std::string& colName)	const
{
	for(size_t i = 0; i < mColumns.size(); ++i)	{
		const Column& column	= mColumns.at(i);
		if(colName == (const char*)column.colName())
			return &column;
	}
	return nullptr;
}

void Recordset::verify(const Column* column, const std::string& colName)
{
	if(column == nullptr)
		throw Column::NotFound(colName);
	if(column->isNullData())
		throw Column::NullData(colName);
}

int Recordset::getInt(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	return ::atoi(column->colBuf());
}

__int64 Recordset::getInt64(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	return ::_atoi64(column->colBuf());
}

float Recordset::getFloat(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	return (float)::atof(column->colBuf());
}

char Recordset::getChar(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	return *column->colBuf();
}

std::string Recordset::getString(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	return column->colBuf();
}

Stream Recordset::getStream(const std::string& colName)	const
{
	const Column* column	= findColumn(colName);
	verify(column, colName);
	char* colBuf	= column->colBuf();
	const size_t kStrLen	= ::strlen(colBuf);
	assert((kStrLen % 2) == 0);
	const size_t kSize	= kStrLen / 2;
	Stream stream(kSize);
	for(size_t i = 0; i < kSize; ++i)	{
		const size_t kPos	= i * 2;
		char kByteStr[3]	= {	colBuf[kPos],	colBuf[kPos+1],	'\0'	};
		char kByte;
		::sscanf_s(kByteStr, "%x", &kByte);
		stream << kByte;
	}
	return stream;
}

}

}